<?php

 /**
  * @todo нужно переделать класс на вариант а-ля Inweb_Event (???)
  * @todo или просто сделать __set && __get
  * @package JooS
  * @subpackage Event
  */

  final class JooS_Event {
    /**
     * @var string
     */
    private $_name = null;

    /**
     * @var array
     */
    private $_observers;

    /**
     * @var array
     */
    private static $_instances = array();

    private static $_cancelReason = null;

    private $_rollback = null;
    
    /**
     * @param string $name
     */
    private function __construct($name) {
      require_once "JooS/Config.php";

      $this->_name = $name;
      $this->_observers = JooS_Config::getInstance($name)->valueOf() ?: array();
    }

   /**
    * @param string $name
    * @return JooS_Event
    */
    public static function newInstance($name) {
      require_once "JooS/Loader.php";

      if (!is_string($name))
        self::cancel("\$name must be a string");

      $name = JooS_Loader::getClassName("Observers", $name);
      if (!isset(self::$_instances[$name]))
        self::$_instances[$name] = new self($name);

      return clone self::$_instances[$name];
    }

    /**
     * @param string $name
     * @param array $arguments
     * @return bool
     */
    public static function __callStatic($name, $arguments) {
      return call_user_func_array(
        array( self::newInstance($name), "notify" ), $arguments
      );
    }

   /**
    * @throws JooS_Event_Exception
    */
    public static function cancel($reason = null) {
      require_once "JooS/Event/Exception.php";

      $this->_cancelReason = $reason;
      throw new JooS_Event_Exception($reason);
    }

    public static function getCancelReason() {
      $reason = $this->_cancelReason;
      $this->_cancelReason = null;

      return $reason;
    }

   /**
    * @return bool
    */
    public function notify() {
      $arguments = func_get_args();
      try {
        $observers = array_values($this->_observers);
        foreach ($observers as $observer) {
          if (is_array($observer) && is_string($observer[0]))
            JooS_Loader::loadClass($observer[0]);
          if (!is_callable($observer))
            continue;

          call_user_func_array($observer, $arguments);
        }
      }
      catch (JooS_Event_Exception $e) {
        return false;
      }
      return true;
    }

    /**
     * @param callback $observer
     * @param bool $commit
     * @return JooS_Event
     */
    public function attach($observer, $commit = false) {
      if (!in_array($observer, $this->_observers)) {
        $this->_observers[] = $observer;
        if ($commit)
          $this->commit(true);
      }
      return $this;
    }

    /**
     *
     * @param callback $observer
     * @param bool $commit
     * @return JooS_Event 
     */
    public function detach($observer, $commit = false) {
      $key = array_search($observer, $this->_observers);
      if ($key !== false) {
        unset($this->_observers[$key]);
        if ($commit)
          $this->commit(true);
      }

      return $this;
    }

   /**
    * @return JooS_Event
    */
    public function clear() {
      $this->_observers = array();
      return $this;
    }
    
    /**
     * @param bool $permanent
     * @return JooS_Event 
     */
    public function commit($permanent = false) {
      $this->_rollback = $permanent ? null : self::$_instances[$this->_name];
      self::$_instances[$this->_name] = $this;

      return $this;
    }

   /**
    * @return JooS_Event
    */
    public function rollback() {
      if (!is_null($this->_rollback)) {
        self::$_instances[$this->_name] = $this->_rollback;
        $this->_rollback = null;
      }
      return $this;
    }

   /**
    * @return string
    */
    public function name() {
      return $this->_name;
    }

   /**
    * @return array
    */
    public function observers() {
      return $this->_observers;
    }

    private function __clone() {
      if (is_object($this->_observers))
        $this->_observers = clone $this->_observers;
    }
  }
